package sw

import (
	"errors"
	"fmt"
	"github.com/SkyAPM/go2sky"
	"github.com/SkyAPM/go2sky/reporter"
	"github.com/gin-gonic/gin"
	"github.com/labstack/gommon/log"
	"net"
	v3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
	"strconv"
	"sync"
	"time"
)

const (
	httpServerComponentID int32 = 49
	httpClientComponentID int32 = 2
	SWTraceId = "SkywalkingTraceId"
)

type routeInfo struct {
	operationName string
}

type middleware struct {
	routeMap     map[string]map[string]routeInfo
	routeMapOnce sync.Once
}


var SkyWalkingReporter go2sky.Reporter
var RunTrace *go2sky.Tracer
var PeerIp string


func InitSkyWalking(endpoint, authToken, appName string) {
	var err error
	SkyWalkingReporter, err = reporter.NewGRPCReporter(endpoint, reporter.WithAuthentication(authToken))

	if err != nil {
		log.Fatalf("skywalking init error %v", err)
	}

	RunTrace, err = go2sky.NewTracer(appName, go2sky.WithReporter(SkyWalkingReporter))
	if err != nil {
		log.Fatalf("skywalking trace init error %v", err)
	}
	PeerIp, _ = GetLocalIP()

}

func CloseReport(){
	SkyWalkingReporter.Close()
}


func ExitSpanRequestDo(c *gin.Context, operaName string, method string, url string) (span go2sky.Span) {
	if RunTrace == nil {
		return
	}

	span, _ = RunTrace.CreateExitSpan(c.Request.Context(), operaName, PeerIp, func(headerKey, headerVal string) error {
		c.Request.Header.Set(headerKey, headerVal)
		return nil
	})

	span.SetComponent(httpClientComponentID)
	span.Tag(go2sky.TagHTTPMethod, method)
	span.Tag(go2sky.TagURL, url)

	return
}



//Middleware gin middleware return HandlerFunc  with sw.
func EntrySpan(engine *gin.Engine) gin.HandlerFunc {
	tracer := RunTrace
	if engine == nil || tracer == nil {
		return func(c *gin.Context) {
			c.Next()
		}
	}
	m := new(middleware)

	return func(c *gin.Context) {
		m.routeMapOnce.Do(func() {
			routes := engine.Routes()
			rm := make(map[string]map[string]routeInfo)
			for _, r := range routes {
				mm := rm[r.Method]
				if mm == nil {
					mm = make(map[string]routeInfo)
					rm[r.Method] = mm
				}
				mm[r.Handler] = routeInfo{
					operationName: fmt.Sprintf("/%s%s", r.Method, r.Path),
				}
			}
			m.routeMap = rm
		})
		var operationName string
		handlerName := c.HandlerName()
		if routeInfo, ok := m.routeMap[c.Request.Method][handlerName]; ok {
			operationName = routeInfo.operationName
		}
		if operationName == "" {
			operationName = c.Request.Method
		}
		span, ctx, err := tracer.CreateEntrySpan(c.Request.Context(), operationName, func(headerKey string) (string, error) {
			return c.Request.Header.Get(headerKey), nil
		})
		if err != nil {
			c.Next()
			return
		}

		traceId := go2sky.TraceID(ctx)
		c.Set(SWTraceId, traceId)
		span.SetComponent(httpServerComponentID)
		span.SetPeer(PeerIp)
		span.SetSpanLayer(v3.SpanLayer_Http)
		span.Tag(go2sky.TagHTTPMethod, c.Request.Method)
		span.Tag(go2sky.TagURL, c.Request.Host+c.Request.URL.Path)

		c.Request = c.Request.WithContext(ctx)

		c.Next()

		if len(c.Errors) > 0 {
			span.Error(time.Now(), c.Errors.String())
		}
		span.Tag(go2sky.TagStatusCode, strconv.Itoa(c.Writer.Status()))
		span.End()
	}
}



// 获取本机网卡IP
func GetLocalIP() (ipv4 string, err error) {
	var (
		addrs   []net.Addr
		addr    net.Addr
		ipNet   *net.IPNet // IP地址
		isIpNet bool
	)
	// 获取所有网卡
	if addrs, err = net.InterfaceAddrs(); err != nil {
		return
	}
	// 取第一个非lo的网卡IP
	for _, addr = range addrs {
		// 这个网络地址是IP地址: ipv4, ipv6
		if ipNet, isIpNet = addr.(*net.IPNet); isIpNet && !ipNet.IP.IsLoopback() {
			// 跳过IPV6
			if ipNet.IP.To4() != nil {
				ipv4 = ipNet.IP.String() // 192.168.1.1
				return
			}
		}
	}

	return "0.0.0.0", errors.New("ip not found")
}